#pragma once
#include <Math/Random.hpp>
#include <Math/Vector3.hpp>
#include <Math/Math.hpp>
#include <Math/HashFunction.hpp>
namespace zzz{
template <typename T>
class NoisePerlin {
public:
  NoisePerlin():seed_(TimeSeed()),type_(NOISE_PERLIN),octaves_(1),lambda_(1),omega_(0.5){}
  NoisePerlin(const zuint seed):seed_(seed),type_(NOISE_PERLIN),octaves_(1),lambda_(1),omega_(0.5){}
  NoisePerlin(const NoisePerlin &noise) {
    type_ = noise.type_;
    seed_ = noise.seed_;
    octaves_ = noise.octaves_;
    lambda_ = noise.lambda_;
    omega_ = noise.omega_;
  }
  ~NoisePerlin(){}

  //1d noise field
  T Value(const T x) const{return Value(Vector<3,T>(x,0,0));}
  //2d noise field
  T Value(const T x,const T y) const{return Value(Vector<3,T>(x,y,0));}
  //3d noise field
  T Value(const T x,const T y,const T z) const{return Value(Vector<3,T>(x,y,z));}
  //3d noise field
  T Value(const Vector<3,T> &pos) const {
    const zuint x = (zuint) floor(pos.x());
    const zuint y = (zuint) floor(pos.y());
    const zuint z = (zuint) floor(pos.z());
    const T fx = pos.x()<1 ? fmod(pos.x(),1.0) + 1 : fmod(pos.x(),1.0);
    const T fy = pos.y()<1 ? fmod(pos.y(),1.0) + 1 : fmod(pos.y(),1.0);
    const T fz = pos.z()<1 ? fmod(pos.z(),1.0) + 1 : fmod(pos.z(),1.0);
    return Value(x,y,z,0,fx,fy,fz);
  }

  /*!
  \brief 1D noise field on tiled integer lattice
  \param x Integer lattice position in x
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  */
  T Value(const zuint x, const zuint fractionalBits, const T fx = 0.0) const;

  /*!
  \brief 2D noise field on tiled integer lattice
  \param x Integer lattice position in x
  \param y Integer lattice position in y
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  \param fy Floating-point fraction in y
  */
  T Value(const zuint x,const zuint y,const zuint fractionalBits,const T fx = 0.0,const T fy = 0.0) const;

  /*!
  \brief 3D noise field on tiled integer lattice
  \param x Integer lattice position in x
  \param y Integer lattice position in y
  \param z Integer lattice position in z
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  \param fy Floating-point fraction in y
  \param fz Floating-point fraction in z
  */
  T Value(const zuint x,const zuint y,const zuint z,const zuint fractionalBits,const T fx = 0.0,const T fy = 0.0,const T fz = 0.0) const;

  //
  // Configuration
  //

  typedef enum{NOISE_PERLIN, NOISE_FBM, NOISE_TURBULENCE} NoiseType;

  NoiseType type_;  ///< Noise type
  zuint seed_;    ///< Unique seed for noise field
  zuint octaves_;  ///< Number of harmonics
  zuint lambda_;  ///< Domain scale factor for subsequent harmonics
  T omega_;  ///< Range scale factor for subsequent harmonics

  const static Vector<3,T> vector_[16]; ///< Fixed set of possible gradients

  /*!
  \brief 1D Perlin noise field
  \param x Integer lattice position in x
  \param y Integer lattice position in y
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  \param fy Floating-point fraction in y
  */
  static T PerlinNoise(const zuint seed,const zuint x,const zuint fractionalBits,const T fx = 0.0);

  /*!
  \brief 2D Perlin noise field
  \param x Integer lattice position in x
  \param y Integer lattice position in y
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  \param fy Floating-point fraction in y
  */
  static T PerlinNoise(const zuint seed,const zuint x,const zuint y,const zuint fractionalBits,const T fx = 0.0,const T fy = 0.0);

  /*!
  \brief 3D Perlin noise field
  \param x Integer lattice position in x
  \param y Integer lattice position in y
  \param z Integer lattice position in z
  \param fractionalBits Number of least significant bits used for fraction
  \param fx Floating-point fraction in x
  \param fy Floating-point fraction in y
  \param fz Floating-point fraction in z
  */
  static T PerlinNoise(const zuint seed,const zuint x,const zuint y,const zuint z,const zuint fractionalBits,const T fx = 0.0,const T fy = 0.0,const T fz = 0.0);

  /// 1D integer lattice gradient index
  static inline zuint Hash(const zuint seed,const zuint x)
  {
    return HashWangUInt(x^seed)%16;
  }

  /// 2D integer lattice gradient index
  static inline zuint Hash(const zuint seed,const zuint x,const zuint y)
  {
    return HashWangUInt(x^HashWangUInt(y^seed))%16;
  }

  /// 3D integer lattice gradient index
  static inline zuint Hash(const zuint seed,const zuint x,const zuint y,const zuint z)
  {
    return HashWangUInt(x^HashWangUInt(y^HashWangUInt(z^seed)))%16;
  }

  /// Ease in-out curve for interpolation
  inline static T Scurve(T t)
  {
    const T t3 = t*t*t;
    const T t4 = t3*t;
    const T t5 = t4*t;
    return 6*t5 - 15*t4 + 10*t3;
  }
};
//////////////////////////////////////////////////////////////////////////
template<typename T>
T NoisePerlin<T>::Value(const zuint x,const zuint y,const zuint z,const zuint fractionalBits,const T fx,const T fy,const T fz) const
{
  switch (type_)
  {
  default:
  case NOISE_PERLIN:
    return PerlinNoise(seed_,x,y,z,fractionalBits,fx,fy,fz);
  case NOISE_FBM:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T ffy = fy;
      T ffz = fz;
      T val = PerlinNoise(seed_,x,y,z,f,ffx,ffy,ffz);
      // Add subsequent harmonics
      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda
          f -= lambda_;
          ffx *= sf;
          ffy *= sf;
          ffz *= sf;
          // Add scaled harmonic
          if (f>=0)
          {
            scale *= omega_;
            val += PerlinNoise(seed_^i,x,y,z,f,ffx,ffy,ffz)*scale;
          }
        }
      return val;
    }
  case NOISE_TURBULENCE:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T ffy = fy;
      T ffz = fz;
      T val = fabs(PerlinNoise(seed_,x,y,z,f,ffx,ffy,ffz));
      // Add subsequent harmonics
      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda
          f -= lambda_;
          ffx *= sf;
          ffy *= sf;
          ffz *= sf;
          // Add scaled harmonic
          if (f>=0)
          {
            scale *= omega_;
            val += Abs(PerlinNoise(seed_^i,x,y,z,f,ffx,ffy,ffz)*scale);
          }
        }
      return val;
    }
  }
}

template <typename T>
T NoisePerlin<T>::Value(const zuint x,const zuint y,const zuint fractionalBits,const T fx,const T fy) const
{
  switch (type_)
  {
  default:
  case NOISE_PERLIN:
    return PerlinNoise(seed_,x,y,fractionalBits,fx,fy);
  case NOISE_FBM:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T ffy = fy;
      T val = PerlinNoise(seed_,x,y,f,ffx,ffy);
      // Add subsequent harmonics
      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda
          f -= lambda_;
          ffx *= sf;
          ffy *= sf;
          // Add scaled harmonic
          if (f>=0)
          {
            scale *= omega_;
            val += PerlinNoise(seed_^i,x,y,f,ffx,ffy)*scale;
          }
        }
      return val;
    }
  case NOISE_TURBULENCE:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T ffy = fy;
      T val = fabs(PerlinNoise(seed_,x,y,f,ffx,ffy));
      // Add subsequent harmonics
      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda
          f -= lambda_;
          ffx *= sf;
          ffy *= sf;
          // Add scaled harmonic
          if (f>=0)
          {
            scale *= omega_;
            val += Abs(PerlinNoise(seed_^i,x,y,f,ffx,ffy)*scale);
          }
        }
      return val;
    }
  }
}

template <typename T>
T NoisePerlin<T>::Value(const zuint x,const zuint fractionalBits,const T fx) const
{
  switch (type_)
  {
  default:
  case NOISE_PERLIN:
    return PerlinNoise(seed_,x,fractionalBits,fx);
  case NOISE_FBM:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T val = PerlinNoise(seed_,x,f,ffx);
      // Add subsequent harmonics
      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda
          f -= lambda_;
          ffx *= sf;
          // Add scaled harmonic
          if (f>=0)
          {
            scale *= omega_;
            val += PerlinNoise(seed_^i,x,f,ffx)*scale;
          }
        }
      return val;
    }
  case NOISE_TURBULENCE:
    {
      const T sf = 1.0/(1<<lambda_);
      zuint f = fractionalBits;
      T scale = 1.0;
      T ffx = fx;
      T val = Abs(PerlinNoise(seed_,x,f,ffx));

      // Add subsequent harmonics

      if (octaves_>0)
        for (zuint i=1; i<octaves_; i++)
        {
          // <x,y,z>/2^lambda

          f -= lambda_;

          ffx *= sf;

          // Add scaled harmonic

          if (f>=0)
          {
            scale *= omega_;
            val += Abs(PerlinNoise(seed_^i,x,f,ffx)*scale);
          }
        }
      return val;
    }
  }
}


}
